/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
 */
// cvar.c -- dynamic variable tracking

#include "q_shared.h"
#include "qcommon.h"

cvar_t *cvar_vars = NULL;
cvar_t *cvar_cheats;
int cvar_modifiedFlags;

#define	MAX_CVARS	1024
cvar_t cvar_indexes[MAX_CVARS];
int cvar_numIndexes;

#define FILE_HASH_SIZE		256
static cvar_t *hashTable[FILE_HASH_SIZE];

cvar_t *Cvar_Set2(const char *var_name, const char *value, qboolean force);

/*
================
return a hash value for the filename
================
 */
static long generateHashValue(const char *fname) {
    int i;
    long hash;
    char letter;

    hash = 0;
    i = 0;
    while (fname[i] != '\0') {
        letter = tolower(fname[i]);
        hash += (long) (letter)*(i + 119);
        i++;
    }
    hash &= (FILE_HASH_SIZE - 1);
    return hash;
}

/*
============
Cvar_ValidateString
============
 */
static qboolean Cvar_ValidateString(const char *s) {
    if (!s) {
        return qfalse;
    }
    if (strchr(s, '\\')) {
        return qfalse;
    }
    if (strchr(s, '\"')) {
        return qfalse;
    }
    if (strchr(s, ';')) {
        return qfalse;
    }
    return qtrue;
}

/*
============
Cvar_FindVar
============
 */
static cvar_t *Cvar_FindVar(const char *var_name) {
    cvar_t *var;
    long hash;

    hash = generateHashValue(var_name);

    for (var = hashTable[hash]; var; var = var->hashNext) {
        if (!Q_stricmp(var_name, var->name)) {
            return var;
        }
    }

    return NULL;
}

/*
============
Cvar_VariableValue
============
 */
float Cvar_VariableValue(const char *var_name) {
    cvar_t *var;

    var = Cvar_FindVar(var_name);
    if (!var)
        return 0;
    return var->value;
}

/*
============
Cvar_VariableIntegerValue
============
 */
int Cvar_VariableIntegerValue(const char *var_name) {
    cvar_t *var;

    var = Cvar_FindVar(var_name);
    if (!var)
        return 0;
    return var->integer;
}

/*
============
Cvar_VariableString
============
 */
char *Cvar_VariableString(const char *var_name) {
    cvar_t *var;

    var = Cvar_FindVar(var_name);
    if (!var)
        return "";
    return var->string;
}

/*
============
Cvar_VariableStringBuffer
============
 */
void Cvar_VariableStringBuffer(const char *var_name, char *buffer, int bufsize) {
    cvar_t *var;

    var = Cvar_FindVar(var_name);
    if (!var) {
        *buffer = 0;
    } else {
        Q_strncpyz(buffer, var->string, bufsize);
    }
}

/*
============
Cvar_Flags
============
 */
int Cvar_Flags(const char *var_name) {
    cvar_t *var;

    if (!(var = Cvar_FindVar(var_name)))
        return CVAR_NONEXISTENT;
    else
        return var->flags;
}

/*
============
Cvar_CommandCompletion
============
 */
void Cvar_CommandCompletion(void (*callback)(const char *s)) {
    cvar_t *cvar;

    for (cvar = cvar_vars; cvar; cvar = cvar->next) {
        if (cvar->name)
            callback(cvar->name);
    }
}

/*
============
Cvar_Validate
============
 */
static const char *Cvar_Validate(cvar_t *var,
        const char *value, qboolean warn) {
    static char s[ MAX_CVAR_VALUE_STRING ];
    float valuef;
    qboolean changed = qfalse;

    if (!var->validate)
        return value;

    if (!value)
        return value;

    if (Q_isanumber(value)) {
        valuef = atof(value);

        if (var->integral) {
            if (!Q_isintegral(valuef)) {
                if (warn)
                    Com_Printf("WARNING: cvar '%s' must be integral", var->name);

                valuef = (int) valuef;
                changed = qtrue;
            }
        }
    } else {
        if (warn)
            Com_Printf("WARNING: cvar '%s' must be numeric", var->name);

        valuef = atof(var->resetString);
        changed = qtrue;
    }

    if (valuef < var->min) {
        if (warn) {
            if (changed)
                Com_Printf(" and is");
            else
                Com_Printf("WARNING: cvar '%s'", var->name);

            if (Q_isintegral(var->min))
                Com_Printf(" out of range (min %d)", (int) var->min);
            else
                Com_Printf(" out of range (min %f)", var->min);
        }

        valuef = var->min;
        changed = qtrue;
    } else if (valuef > var->max) {
        if (warn) {
            if (changed)
                Com_Printf(" and is");
            else
                Com_Printf("WARNING: cvar '%s'", var->name);

            if (Q_isintegral(var->max))
                Com_Printf(" out of range (max %d)", (int) var->max);
            else
                Com_Printf(" out of range (max %f)", var->max);
        }

        valuef = var->max;
        changed = qtrue;
    }

    if (changed) {
        if (Q_isintegral(valuef)) {
            Com_sprintf(s, sizeof ( s), "%d", (int) valuef);

            if (warn)
                Com_Printf(", setting to %d\n", (int) valuef);
        } else {
            Com_sprintf(s, sizeof ( s), "%f", valuef);

            if (warn)
                Com_Printf(", setting to %f\n", valuef);
        }

        return s;
    } else
        return value;
}

/*
============
Cvar_Get

If the variable already exists, the value will not be set unless CVAR_ROM
The flags will be or'ed in if the variable exists.
============
 */
cvar_t *Cvar_Get(const char *var_name, const char *var_value, int flags) {
    cvar_t *var;
    long hash;
    int index;

    if (!var_name || !var_value) {
        Com_Error(ERR_FATAL, "Cvar_Get: NULL parameter");
    }

    if (!Cvar_ValidateString(var_name)) {
        Com_Printf("invalid cvar name string: %s\n", var_name);
        var_name = "BADNAME";
    }

#if 0		// FIXME: values with backslash happen
    if (!Cvar_ValidateString(var_value)) {
        Com_Printf("invalid cvar value string: %s\n", var_value);
        var_value = "BADVALUE";
    }
#endif

    var = Cvar_FindVar(var_name);

    if (var) {
        var_value = Cvar_Validate(var, var_value, qfalse);

        // if the C code is now specifying a variable that the user already
        // set a value for, take the new value as the reset value
        if (var->flags & CVAR_USER_CREATED) {
            var->flags &= ~CVAR_USER_CREATED;
            Z_Free(var->resetString);
            var->resetString = CopyString(var_value);

            if (flags & CVAR_ROM) {
                // this variable was set by the user,
                // so force it to value given by the engine.

                if (var->latchedString)
                    Z_Free(var->latchedString);

                var->latchedString = CopyString(var_value);
            }
        }

        // Make sure the game code cannot mark engine-added variables as gamecode vars
        if (var->flags & CVAR_VM_CREATED) {
            if (!(flags & CVAR_VM_CREATED))
                var->flags &= ~CVAR_VM_CREATED;
        } else {
            if (flags & CVAR_VM_CREATED)
                flags &= ~CVAR_VM_CREATED;
        }

        var->flags |= flags;

        // only allow one non-empty reset string without a warning
        if (!var->resetString[0]) {
            // we don't have a reset string yet
            Z_Free(var->resetString);
            var->resetString = CopyString(var_value);
        } else if (var_value[0] && strcmp(var->resetString, var_value)) {
            Com_DPrintf("Warning: cvar \"%s\" given initial values: \"%s\" and \"%s\"\n",
                    var_name, var->resetString, var_value);
        }
        // if we have a latched string, take that value now
        if (var->latchedString) {
            char *s;

            s = var->latchedString;
            var->latchedString = NULL; // otherwise cvar_set2 would free it
            Cvar_Set2(var_name, s, qtrue);
            Z_Free(s);
        }

        // ZOID--needs to be set so that cvars the game sets as
        // SERVERINFO get sent to clients
        cvar_modifiedFlags |= flags;

        return var;
    }

    //
    // allocate a new cvar
    //

    // find a free cvar
    for (index = 0; index < MAX_CVARS; index++) {
        if (!cvar_indexes[index].name)
            break;
    }

    if (index >= MAX_CVARS) {
        if (!com_errorEntered)
            Com_Error(ERR_FATAL, "Error: Too many cvars, cannot create a new one!");

        return NULL;
    }

    var = &cvar_indexes[index];

    if (index >= cvar_numIndexes)
        cvar_numIndexes = index + 1;

    var->name = CopyString(var_name);
    var->string = CopyString(var_value);
    var->modified = qtrue;
    var->modificationCount = 1;
    var->value = atof(var->string);
    var->integer = atoi(var->string);
    var->resetString = CopyString(var_value);
    var->validate = qfalse;

    // link the variable in
    var->next = cvar_vars;
    if (cvar_vars)
        cvar_vars->prev = var;

    var->prev = NULL;
    cvar_vars = var;

    var->flags = flags;
    // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo)
    cvar_modifiedFlags |= var->flags;

    hash = generateHashValue(var_name);
    var->hashIndex = hash;

    var->hashNext = hashTable[hash];
    if (hashTable[hash])
        hashTable[hash]->hashPrev = var;

    var->hashPrev = NULL;
    hashTable[hash] = var;

    return var;
}

/*
============
Cvar_Print

Prints the value, default, and latched string of the given variable
============
 */
void Cvar_Print(cvar_t *v) {
    Com_Printf("\"%s\" is:\"%s" S_COLOR_WHITE "\"",
            v->name, v->string);

    if (!(v->flags & CVAR_ROM)) {
        if (!Q_stricmp(v->string, v->resetString)) {
            Com_Printf(", the default");
        } else {
            Com_Printf(" default:\"%s" S_COLOR_WHITE "\"",
                    v->resetString);
        }
    }

    Com_Printf("\n");

    if (v->latchedString) {
        Com_Printf("latched: \"%s\"\n", v->latchedString);
    }
}

/*
============
Cvar_Set2
============
 */
cvar_t *Cvar_Set2(const char *var_name, const char *value, qboolean force) {
    cvar_t *var;

    //	Com_DPrintf( "Cvar_Set2: %s %s\n", var_name, value );

    if (!Cvar_ValidateString(var_name)) {
        Com_Printf("invalid cvar name string: %s\n", var_name);
        var_name = "BADNAME";
    }

#if 0	// FIXME
    if (value && !Cvar_ValidateString(value)) {
        Com_Printf("invalid cvar value string: %s\n", value);
        var_value = "BADVALUE";
    }
#endif

    var = Cvar_FindVar(var_name);
    if (!var) {
        if (!value) {
            return NULL;
        }
        // create it
        if (!force) {
            return Cvar_Get(var_name, value, CVAR_USER_CREATED);
        } else {
            return Cvar_Get(var_name, value, 0);
        }
    }

    if (!value) {
        value = var->resetString;
    }

    value = Cvar_Validate(var, value, qtrue);

    if ((var->flags & CVAR_LATCH) && var->latchedString) {
        if (!strcmp(value, var->string)) {
            Z_Free(var->latchedString);
            var->latchedString = NULL;
            return var;
        }

        if (!strcmp(value, var->latchedString))
            return var;
    } else if (!strcmp(value, var->string))
        return var;

    // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo)
    cvar_modifiedFlags |= var->flags;

    if (!force) {
        if (var->flags & CVAR_ROM) {
            Com_Printf("%s is read only.\n", var_name);
            return var;
        }

        if (var->flags & CVAR_INIT) {
            Com_Printf("%s is write protected.\n", var_name);
            return var;
        }

        if (var->flags & CVAR_LATCH) {
            if (var->latchedString) {
                if (strcmp(value, var->latchedString) == 0)
                    return var;
                Z_Free(var->latchedString);
            } else {
                if (strcmp(value, var->string) == 0)
                    return var;
            }

            Com_Printf("%s will be changed upon restarting.\n", var_name);
            var->latchedString = CopyString(value);
            var->modified = qtrue;
            var->modificationCount++;
            return var;
        }

        if ((var->flags & CVAR_CHEAT) && !cvar_cheats->integer) {
            Com_Printf("%s is cheat protected.\n", var_name);
            return var;
        }

    } else {
        if (var->latchedString) {
            Z_Free(var->latchedString);
            var->latchedString = NULL;
        }
    }

    if (!strcmp(value, var->string))
        return var; // not changed

    var->modified = qtrue;
    var->modificationCount++;

    Z_Free(var->string); // free the old value string

    var->string = CopyString(value);
    var->value = atof(var->string);
    var->integer = atoi(var->string);

    return var;
}

/*
============
Cvar_Set
============
 */
void Cvar_Set(const char *var_name, const char *value) {
    Cvar_Set2(var_name, value, qtrue);
}

/*
============
Cvar_SetLatched
============
 */
void Cvar_SetLatched(const char *var_name, const char *value) {
    Cvar_Set2(var_name, value, qfalse);
}

/*
============
Cvar_SetValue
============
 */
void Cvar_SetValue(const char *var_name, float value) {
    char val[32];

    if (value == (int) value) {
        Com_sprintf(val, sizeof (val), "%i", (int) value);
    } else {
        Com_sprintf(val, sizeof (val), "%f", value);
    }
    Cvar_Set(var_name, val);
}

/*
============
Cvar_Reset
============
 */
void Cvar_Reset(const char *var_name) {
    Cvar_Set2(var_name, NULL, qfalse);
}

/*
============
Cvar_ForceReset
============
 */
void Cvar_ForceReset(const char *var_name) {
    Cvar_Set2(var_name, NULL, qtrue);
}

/*
============
Cvar_SetCheatState

Any testing variables will be reset to the safe values
============
 */
void Cvar_SetCheatState(void) {
    cvar_t *var;

    // set all default vars to the safe value
    for (var = cvar_vars; var; var = var->next) {
        if (var->flags & CVAR_CHEAT) {
            // the CVAR_LATCHED|CVAR_CHEAT vars might escape the reset here
            // because of a different var->latchedString
            if (var->latchedString) {
                Z_Free(var->latchedString);
                var->latchedString = NULL;
            }
            if (strcmp(var->resetString, var->string))
                Cvar_Set(var->name, var->resetString);
        }
    }
}

/*
============
Cvar_Command

Handles variable inspection and changing from the console
============
 */
qboolean Cvar_Command(void) {
    cvar_t *v;

    // check variables
    v = Cvar_FindVar(Cmd_Argv(0));
    if (!v) {
        return qfalse;
    }

    // perform a variable print or set
    if (Cmd_Argc() == 1) {
        Cvar_Print(v);
        return qtrue;
    }

    // set the value if forcing isn't required
    Cvar_Set2(v->name, Cmd_Args(), qfalse);
    return qtrue;
}

/*
============
Cvar_Print_f

Prints the contents of a cvar 
(preferred over Cvar_Command where cvar names and commands conflict)
============
 */
void Cvar_Print_f(void) {
    char *name;
    cvar_t *cv;

    if (Cmd_Argc() != 2) {
        Com_Printf("usage: print <variable>\n");
        return;
    }

    name = Cmd_Argv(1);

    cv = Cvar_FindVar(name);

    if (cv)
        Cvar_Print(cv);
    else
        Com_Printf("Cvar %s does not exist.\n", name);
}

/*
============
Cvar_Toggle_f

Toggles a cvar for easy single key binding, optionally through a list of
given values
============
 */
void Cvar_Toggle_f(void) {
    int i, c = Cmd_Argc();
    char *curval;

    if (c < 2) {
        Com_Printf("usage: toggle <variable> [value1, value2, ...]\n");
        return;
    }

    if (c == 2) {
        Cvar_Set2(Cmd_Argv(1), va("%d",
                !Cvar_VariableValue(Cmd_Argv(1))),
                qfalse);
        return;
    }

    if (c == 3) {
        Com_Printf("toggle: nothing to toggle to\n");
        return;
    }

    curval = Cvar_VariableString(Cmd_Argv(1));

    // don't bother checking the last arg for a match since the desired
    // behaviour is the same as no match (set to the first argument)
    for (i = 2; i + 1 < c; i++) {
        if (strcmp(curval, Cmd_Argv(i)) == 0) {
            Cvar_Set2(Cmd_Argv(1), Cmd_Argv(i + 1), qfalse);
            return;
        }
    }

    // fallback
    Cvar_Set2(Cmd_Argv(1), Cmd_Argv(2), qfalse);
}

/*
============
Cvar_Set_f

Allows setting and defining of arbitrary cvars from console, even if they
weren't declared in C code.
============
 */
void Cvar_Set_f(void) {
    int c;
    char *cmd;
    cvar_t *v;

    c = Cmd_Argc();
    cmd = Cmd_Argv(0);

    if (c < 2) {
        Com_Printf("usage: %s <variable> <value>\n", cmd);
        return;
    }
    if (c == 2) {
        Cvar_Print_f();
        return;
    }

    v = Cvar_Set2(Cmd_Argv(1), Cmd_ArgsFrom(2), qfalse);
    if (!v) {
        return;
    }
    switch (cmd[3]) {
        case 'a':
            if (!(v->flags & CVAR_ARCHIVE)) {
                v->flags |= CVAR_ARCHIVE;
                cvar_modifiedFlags |= CVAR_ARCHIVE;
            }
            break;
        case 'u':
            if (!(v->flags & CVAR_USERINFO)) {
                v->flags |= CVAR_USERINFO;
                cvar_modifiedFlags |= CVAR_USERINFO;
            }
            break;
        case 's':
            if (!(v->flags & CVAR_SERVERINFO)) {
                v->flags |= CVAR_SERVERINFO;
                cvar_modifiedFlags |= CVAR_SERVERINFO;
            }
            break;
    }
}

/*
============
Cvar_Reset_f
============
 */
void Cvar_Reset_f(void) {
    if (Cmd_Argc() != 2) {
        Com_Printf("usage: reset <variable>\n");
        return;
    }
    Cvar_Reset(Cmd_Argv(1));
}

/*
============
Cvar_WriteVariables

Appends lines containing "set variable value" for all variables
with the archive flag set to qtrue.
============
 */
void Cvar_WriteVariables(fileHandle_t f) {
    cvar_t *var;
    char buffer[1024];

    for (var = cvar_vars; var; var = var->next) {
        if (!var->name || Q_stricmp(var->name, "cl_cdkey") == 0)
            continue;

        if (var->flags & CVAR_ARCHIVE) {
            // write the latched value, even if it hasn't taken effect yet
            if (var->latchedString) {
                if (strlen(var->name) + strlen(var->latchedString) + 10 > sizeof ( buffer)) {
                    Com_Printf(S_COLOR_YELLOW "WARNING: value of variable "
                            "\"%s\" too long to write to file\n", var->name);
                    continue;
                }
                Com_sprintf(buffer, sizeof (buffer), "seta %s \"%s\"\n", var->name, var->latchedString);
            } else {
                if (strlen(var->name) + strlen(var->string) + 10 > sizeof ( buffer)) {
                    Com_Printf(S_COLOR_YELLOW "WARNING: value of variable "
                            "\"%s\" too long to write to file\n", var->name);
                    continue;
                }
                Com_sprintf(buffer, sizeof (buffer), "seta %s \"%s\"\n", var->name, var->string);
            }
            FS_Write(buffer, strlen(buffer), f);
        }
    }
}

/*
============
Cvar_List_f
============
 */
void Cvar_List_f(void) {
    cvar_t *var;
    int i;
    char *match;

    if (Cmd_Argc() > 1) {
        match = Cmd_Argv(1);
    } else {
        match = NULL;
    }

    i = 0;
    for (var = cvar_vars; var; var = var->next, i++) {
        if (!var->name || (match && !Com_Filter(match, var->name, qfalse)))
            continue;

        if (var->flags & CVAR_SERVERINFO) {
            Com_Printf("S");
        } else {
            Com_Printf(" ");
        }
        if (var->flags & CVAR_SYSTEMINFO) {
            Com_Printf("s");
        } else {
            Com_Printf(" ");
        }
        if (var->flags & CVAR_USERINFO) {
            Com_Printf("U");
        } else {
            Com_Printf(" ");
        }
        if (var->flags & CVAR_ROM) {
            Com_Printf("R");
        } else {
            Com_Printf(" ");
        }
        if (var->flags & CVAR_INIT) {
            Com_Printf("I");
        } else {
            Com_Printf(" ");
        }
        if (var->flags & CVAR_ARCHIVE) {
            Com_Printf("A");
        } else {
            Com_Printf(" ");
        }
        if (var->flags & CVAR_LATCH) {
            Com_Printf("L");
        } else {
            Com_Printf(" ");
        }
        if (var->flags & CVAR_CHEAT) {
            Com_Printf("C");
        } else {
            Com_Printf(" ");
        }
        if (var->flags & CVAR_USER_CREATED) {
            Com_Printf("?");
        } else {
            Com_Printf(" ");
        }

        Com_Printf(" %s \"%s\"\n", var->name, var->string);
    }

    Com_Printf("\n%i total cvars\n", i);
    Com_Printf("%i cvar indexes\n", cvar_numIndexes);
}

/*
============
Cvar_Unset

Unsets a cvar
============
 */

cvar_t *Cvar_Unset(cvar_t *cv) {
    cvar_t *next = cv->next;

    if (cv->name)
        Z_Free(cv->name);
    if (cv->string)
        Z_Free(cv->string);
    if (cv->latchedString)
        Z_Free(cv->latchedString);
    if (cv->resetString)
        Z_Free(cv->resetString);

    if (cv->prev)
        cv->prev->next = cv->next;
    else
        cvar_vars = cv->next;
    if (cv->next)
        cv->next->prev = cv->prev;

    if (cv->hashPrev)
        cv->hashPrev->hashNext = cv->hashNext;
    else
        hashTable[cv->hashIndex] = cv->hashNext;
    if (cv->hashNext)
        cv->hashNext->hashPrev = cv->hashPrev;

    Com_Memset(cv, '\0', sizeof (*cv));

    return next;
}

/*
============
Cvar_Unset_f

Unsets a userdefined cvar
============
 */

void Cvar_Unset_f(void) {
    cvar_t *cv;

    if (Cmd_Argc() != 2) {
        Com_Printf("Usage: %s <varname>\n", Cmd_Argv(0));
        return;
    }

    cv = Cvar_FindVar(Cmd_Argv(1));

    if (!cv)
        return;

    if (cv->flags & CVAR_USER_CREATED)
        Cvar_Unset(cv);
    else
        Com_Printf("Error: %s: Variable %s is not user created.\n", Cmd_Argv(0), cv->name);
}

/*
============
Cvar_Restart

Resets all cvars to their hardcoded values and removes userdefined variables
and variables added via the VMs if requested.
============
 */

void Cvar_Restart(qboolean unsetVM) {
    cvar_t *curvar;

    curvar = cvar_vars;

    while (curvar) {
        if ((curvar->flags & CVAR_USER_CREATED) ||
                (unsetVM && (curvar->flags & CVAR_VM_CREATED))) {
            // throw out any variables the user/vm created
            curvar = Cvar_Unset(curvar);
            continue;
        }

        if (!(curvar->flags & (CVAR_ROM | CVAR_INIT | CVAR_NORESTART))) {
            // Just reset the rest to their default values.
            Cvar_Set2(curvar->name, curvar->resetString, qfalse);
        }

        curvar = curvar->next;
    }
}

/*
============
Cvar_Restart_f

Resets all cvars to their hardcoded values
============
 */
void Cvar_Restart_f(void) {
    Cvar_Restart(qfalse);
}

/*
=====================
Cvar_InfoString
=====================
 */
char *Cvar_InfoString(int bit) {
    static char info[MAX_INFO_STRING];
    cvar_t *var;

    info[0] = 0;

    for (var = cvar_vars; var; var = var->next) {
        if (var->name && (var->flags & bit))
            Info_SetValueForKey(info, var->name, var->string);
    }

    return info;
}

/*
=====================
Cvar_InfoString_Big

  handles large info strings ( CS_SYSTEMINFO )
=====================
 */
char *Cvar_InfoString_Big(int bit) {
    static char info[BIG_INFO_STRING];
    cvar_t *var;

    info[0] = 0;

    for (var = cvar_vars; var; var = var->next) {
        if (var->name && (var->flags & bit))
            Info_SetValueForKey_Big(info, var->name, var->string);
    }
    return info;
}

/*
=====================
Cvar_InfoStringBuffer
=====================
 */
void Cvar_InfoStringBuffer(int bit, char* buff, int buffsize) {
    Q_strncpyz(buff, Cvar_InfoString(bit), buffsize);
}

/*
=====================
Cvar_CheckRange
=====================
 */
void Cvar_CheckRange(cvar_t *var, float min, float max, qboolean integral) {
    var->validate = qtrue;
    var->min = min;
    var->max = max;
    var->integral = integral;

    // Force an initial range check
    Cvar_Set(var->name, var->string);
}

/*
=====================
Cvar_Register

basically a slightly modified Cvar_Get for the interpreted modules
=====================
 */
void Cvar_Register(vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags) {
    cvar_t *cv;

    // There is code in Cvar_Get to prevent CVAR_ROM cvars being changed by the
    // user. In other words CVAR_ARCHIVE and CVAR_ROM are mutually exclusive
    // flags. Unfortunately some historical game code (including single player
    // baseq3) sets both flags. We unset CVAR_ROM for such cvars.
    if ((flags & (CVAR_ARCHIVE | CVAR_ROM)) == (CVAR_ARCHIVE | CVAR_ROM)) {
        Com_DPrintf(S_COLOR_YELLOW "WARNING: Unsetting CVAR_ROM cvar '%s', "
                "since it is also CVAR_ARCHIVE\n", varName);
        flags &= ~CVAR_ROM;
    }

    cv = Cvar_Get(varName, defaultValue, flags | CVAR_VM_CREATED);

    if (!vmCvar)
        return;

    vmCvar->handle = cv - cvar_indexes;
    vmCvar->modificationCount = -1;
    Cvar_Update(vmCvar);
}

/*
=====================
Cvar_Update

updates an interpreted modules' version of a cvar
=====================
 */
void Cvar_Update(vmCvar_t *vmCvar) {
    cvar_t *cv = NULL;
    assert(vmCvar);

    if ((unsigned) vmCvar->handle >= cvar_numIndexes) {
        Com_Error(ERR_DROP, "Cvar_Update: handle out of range, int = %i, string = %s, value = %f",
                vmCvar->integer, vmCvar->string, vmCvar->value);
    }

    cv = cvar_indexes + vmCvar->handle;

    if (cv->modificationCount == vmCvar->modificationCount) {
        return;
    }
    if (!cv->string) {
        return; // variable might have been cleared by a cvar_restart
    }
    vmCvar->modificationCount = cv->modificationCount;
    if (strlen(cv->string) + 1 > MAX_CVAR_VALUE_STRING)
        Com_Error(ERR_DROP, "Cvar_Update: src %s length %zd exceeds MAX_CVAR_VALUE_STRING",
            cv->string,
            strlen(cv->string));
    Q_strncpyz(vmCvar->string, cv->string, MAX_CVAR_VALUE_STRING);

    vmCvar->value = cv->value;
    vmCvar->integer = cv->integer;
}

/*
==================
Cvar_CompleteCvarName
==================
 */
void Cvar_CompleteCvarName(char *args, int argNum) {
    if (argNum == 2) {
        // Skip "<cmd> "
        char *p = Com_SkipTokens(args, 1, " ");

        if (p > args)
            Field_CompleteCommand(p, qfalse, qtrue);
    }
}

/*
============
Cvar_Init

Reads in all archived cvars
============
 */
void Cvar_Init(void) {
    Com_Memset(cvar_indexes, '\0', sizeof (cvar_indexes));
    Com_Memset(hashTable, '\0', sizeof (hashTable));

    cvar_cheats = Cvar_Get("sv_cheats", "1", CVAR_ROM | CVAR_SYSTEMINFO);

    Cmd_AddCommand("print", Cvar_Print_f);
    Cmd_AddCommand("toggle", Cvar_Toggle_f);
    Cmd_SetCommandCompletionFunc("toggle", Cvar_CompleteCvarName);
    Cmd_AddCommand("set", Cvar_Set_f);
    Cmd_SetCommandCompletionFunc("set", Cvar_CompleteCvarName);
    Cmd_AddCommand("sets", Cvar_Set_f);
    Cmd_SetCommandCompletionFunc("sets", Cvar_CompleteCvarName);
    Cmd_AddCommand("setu", Cvar_Set_f);
    Cmd_SetCommandCompletionFunc("setu", Cvar_CompleteCvarName);
    Cmd_AddCommand("seta", Cvar_Set_f);
    Cmd_SetCommandCompletionFunc("seta", Cvar_CompleteCvarName);
    Cmd_AddCommand("reset", Cvar_Reset_f);
    Cmd_SetCommandCompletionFunc("reset", Cvar_CompleteCvarName);
    Cmd_AddCommand("unset", Cvar_Unset_f);
    Cmd_SetCommandCompletionFunc("unset", Cvar_CompleteCvarName);

    Cmd_AddCommand("cvarlist", Cvar_List_f);
    Cmd_AddCommand("cvar_restart", Cvar_Restart_f);
}
